package com.quick.develop.flowable.service.definition.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.quick.develop.framework.common.page.PageResult;
import com.quick.develop.framework.utils.PageUtils;
import com.quick.develop.framework.utils.StringUtils;
import com.quick.develop.flowable.domain.definition.BpmProcessDefinitionCreateReqDTO;
import com.quick.develop.flowable.domain.definition.BpmProcessDefinitionPageItemRespVO;
import com.quick.develop.flowable.domain.definition.BpmProcessDefinitionPageReqVO;
import com.quick.develop.flowable.domain.definition.BpmProcessDefinitionRespVO;
import com.quick.develop.flowable.framework.cmd.DeploymentCacheCmd;
import com.quick.develop.flowable.framework.convert.definition.BpmProcessDefinitionConvert;
import com.quick.develop.flowable.utils.FlowableUtil;
import com.quick.develop.flowable.service.definition.IBpmProcessDefinitionService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static com.quick.develop.flowable.constant.BpmConstants.BPMN_FILE_SUFFIX;
import static com.quick.develop.flowable.enums.BpmErrorCodeConstants.PROCESS_DEFINITION_KEY_NOT_MATCH;
import static com.quick.develop.flowable.enums.BpmErrorCodeConstants.PROCESS_DEFINITION_NAME_NOT_MATCH;
import static com.quick.develop.framework.common.utils.ServiceExceptionUtils.exception;
import static com.quick.develop.framework.utils.collection.CollectionUtils.addIfNotNull;
import static java.util.Collections.emptyList;

/**
 * @author junyuan.liu
 * @description: 
 * @date 2022/4/22 10:58
 */
@Service
@Slf4j
public class BpmProcessDefinitionServiceImpl implements IBpmProcessDefinitionService {
    

    @Resource
    private RepositoryService repositoryService;
    
    @Resource
    private ManagementService managementService;

    @Override
    public PageResult<BpmProcessDefinitionPageItemRespVO> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageReqVO) {
        ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
        if (StrUtil.isNotBlank(pageReqVO.getKey())) {
            definitionQuery.processDefinitionKey(pageReqVO.getKey());
        }

        // 执行查询
        List<ProcessDefinition> processDefinitions = definitionQuery.orderByProcessDefinitionVersion().desc()
                .listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize());

        if (CollUtil.isEmpty(processDefinitions)) {
            return new PageResult<>(emptyList(), definitionQuery.count());
        }
        // 获得 Deployment Map
        Set<String> deploymentIds = new HashSet<>();
        processDefinitions.forEach(definition -> addIfNotNull(deploymentIds, definition.getDeploymentId()));
        Map<String, Deployment> deploymentMap = getDeploymentMap(deploymentIds);


        // 拼接结果
        long definitionCount = definitionQuery.count();
        return new PageResult<>(BpmProcessDefinitionConvert.INSTANCE.convertList(processDefinitions, deploymentMap), definitionCount);
    }

    @Override
    public ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId) {
        if (StrUtil.isEmpty(deploymentId)) {
            return null;
        }
        return repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
    }

    @Override
    public List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds) {
        if (CollUtil.isEmpty(deploymentIds)) {
            return emptyList();
        }
        return repositoryService.createProcessDefinitionQuery().deploymentIds(deploymentIds).list();
    }

    @Override
    public ProcessDefinition getProcessDefinitionListByDefinitionId(String definitionId) {
        if (StrUtil.isEmpty(definitionId)) {
            return null;
        }
       return repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(definitionId)
                .singleResult();
    }

    @Override
    public List<ProcessDefinition> getProcessDefinitionListByDefinitionIds(Set<String> definitionIds) {
        if (CollUtil.isEmpty(definitionIds)){
            return emptyList();
        }
        return repositoryService.createProcessDefinitionQuery()
                .processDefinitionIds(definitionIds).list();
    }


    @Override
    public ProcessDefinition getProcessDefinition(String id) {
        return repositoryService.getProcessDefinition(id);
    }

    @Override
    public Deployment getDeployment(String id) {
        if (StrUtil.isEmpty(id)) {
            return null;
        }
        return repositoryService.createDeploymentQuery().deploymentId(id).singleResult();
    }

    @Override
    public List<Deployment> getDeployments(Set<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return emptyList();
        }
        List<Deployment> list = new ArrayList<>(ids.size());
        for (String id : ids) {
            addIfNotNull(list, getDeployment(id));
        }
        return list;
    }

    @Override
    public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) {
        return repositoryService.getBpmnModel(processDefinitionId);
    }


    @Override
    public ProcessDefinition getActiveProcessDefinitionByKey(String key) {
        return repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(key)
                // .latestVersion()
                .active()
                .singleResult();
    }

    @Override
    public BpmProcessDefinitionRespVO getActiveProDefByKey(String key) {
        ProcessDefinition definition = this.getActiveProcessDefinitionByKey(key);
        return BpmProcessDefinitionConvert.INSTANCE.convert3(definition);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createProcessDefinition(BpmProcessDefinitionCreateReqDTO createReqDTO) {
        // 创建 Deployment 部署
        Deployment deploy = repositoryService.createDeployment()
                .key(createReqDTO.getKey()).name(createReqDTO.getName())
                .category(createReqDTO.getCategory())
                .tenantId(createReqDTO.getTenantId())
                .addBytes(createReqDTO.getKey() + BPMN_FILE_SUFFIX, createReqDTO.getBpmnBytes())
                .deploy();

        // 设置 ProcessDefinition 的 category 分类
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
        repositoryService.setProcessDefinitionCategory(definition.getId(), createReqDTO.getCategory());
        // 注意 1，ProcessDefinition 的 key 和 name 是通过 BPMN 中的 <bpmn2:process /> 的 id 和 name 决定
        // 注意 2，目前该项目的设计上，需要保证 Model、Deployment、ProcessDefinition 使用相同的 key，保证关联性。
        //          否则，会导致 ProcessDefinition 的分页无法查询到。
        if (!Objects.equals(definition.getKey(), createReqDTO.getKey())) {
            throw exception(PROCESS_DEFINITION_KEY_NOT_MATCH, createReqDTO.getKey(), definition.getKey());
        }
        if (!Objects.equals(definition.getName(), createReqDTO.getName())) {
            throw exception(PROCESS_DEFINITION_NAME_NOT_MATCH, createReqDTO.getName(), definition.getName());
        }

        return definition.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBpmDeployment(String deployId, boolean cascade) {
        Optional.ofNullable(deployId).
                ifPresent(i -> repositoryService.deleteDeployment(i, cascade));
    }

    @Override
    public boolean isProcessDefinitionEquals(BpmProcessDefinitionCreateReqDTO createReqDTO) {
        // 校验 name、description 是否更新
        ProcessDefinition oldProcessDefinition = getActiveProcessDefinitionByKey(createReqDTO.getKey());
        if (oldProcessDefinition == null) {
            return true;
        }
        // 校验 BPMN XML 信息
        BpmnModel newModel = buildBpmnModel(createReqDTO.getBpmnBytes());
        BpmnModel oldModel = getBpmnModelByDefinitionId(oldProcessDefinition.getId());
        // 貌似 flowable 不修改这个也不同。需要看看。 sourceSystemId 不同
        // 最终发现都一致，则返回 true
        return !FlowableUtil.equals(oldModel, newModel);

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProcessDefinitionState(String id, Integer state, boolean suspendProcessInstances) {
        // 激活
        if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) {
            repositoryService.activateProcessDefinitionById(id, suspendProcessInstances, null);
            return;
        }
        // 挂起
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) {
            // suspendProcessInstances = false，进行中的任务，不进行挂起。
            // 原因：只要新的流程不允许发起即可，老流程继续可以执行。
            repositoryService.suspendProcessDefinitionById(id, suspendProcessInstances, null);
            return;
        }
        log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state);

    }

    @Override
    public List<BpmProcessDefinitionRespVO> getProcessDefinitionList(BpmProcessDefinitionPageReqVO listReqVO) {
        // 拼接查询条件
        ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), listReqVO.getSuspensionState())) {
            definitionQuery.suspended();
        } else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), listReqVO.getSuspensionState())) {
            definitionQuery.active();
        }
        // 执行查询
        List<ProcessDefinition> processDefinitions = definitionQuery.list();

        return null;
    }

    @Override
    public PageResult<BpmProcessDefinitionRespVO> getProDefPage(BpmProcessDefinitionPageReqVO pageVo) {
        // 拼接查询条件
        ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), pageVo.getSuspensionState())) {
            definitionQuery.suspended();//挂起
        } else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), pageVo.getSuspensionState())) {
            definitionQuery.active();//激活的
        }
        if (StringUtils.isNotEmpty(pageVo.getName())){
            definitionQuery.processDefinitionNameLike("%" + pageVo.getName() + "%");
        }
        if(StringUtils.isNotEmpty(pageVo.getCategory())){
            definitionQuery.processDefinitionCategoryLike("%" + pageVo.getCategory() + "%");
        }

        List<ProcessDefinition> pds = definitionQuery.orderByProcessDefinitionName().asc()
                .listPage(PageUtils.getStart(pageVo), pageVo.getPageSize());


        return new PageResult<>(BpmProcessDefinitionConvert.INSTANCE.convertList3(pds), definitionQuery.count());

    }

    @Override
    public String getBpmnXmlByDeploymentId(String id) throws IOException {
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(id).singleResult();
        InputStream inputStream = repositoryService
                .getResourceAsStream(definition.getDeploymentId(), definition.getResourceName());
        return IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
    }

    @Override
    public String getBpmnXmlByDefinitionId(String id) {
        String xml = "";
        try {
            InputStream processModel = repositoryService.getProcessModel(id);
            
            xml = IOUtils.toString(processModel, StandardCharsets.UTF_8.name());
            
        }catch (Exception e){
            xml = "";
            e.printStackTrace();
            log.info("通过流程定义ID获取流程图异常：{}，异常信息：{}", id, e.getMessage());
        }
        return xml;
    }

    @Override
    public void clearDeploymentCache(String definitionId) {
        managementService.executeCommand(new DeploymentCacheCmd(definitionId));
    }

    /** 
     * @description 构建对应的 BPMN Model
     * @param bpmnBytes 原始的 BPMN XML 字节数组
     * @return org.flowable.bpmn.model.BpmnModel
     * @author junyuan.liu
     * @date 2022/4/22 16:39
     */ 
    private  BpmnModel buildBpmnModel(byte[] bpmnBytes) {
        // 转换成 BpmnModel 对象
        BpmnXMLConverter converter = new BpmnXMLConverter();
        return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true);
    }


}
